<template>
  <div class="bs-anchor" :class="{underline: type === 'underline'}">
    <div class="bs-anchor-masker" :style="{top: maskTop}"></div>
    <div class="bs-anchor-list">
      <slot></slot>
    </div>
  </div>
</template>
<script setup lang="ts">
import { provide, onMounted, onUnmounted, ref, watch } from 'vue'

export type BsAnchorProps = {
  container?: string | HTMLElement | Window
  offset?: number
  type?: 'defalut' | 'underline'
}
type ChildObj = {
  href: string
  el: HTMLElement
  height: number
  width?: number
  offsetTop: number
}

const props = withDefaults(defineProps<BsAnchorProps>(), {})
/**当前锚点位置 */
const curHref = ref<string>('')
const hrefMap = new Map<string, string>()
const maskTop = ref('8px')

const conatinerDom = ref<HTMLElement | Window>(window)

// 计算每段dom对应的位置及高度
const computAllChild = () => {
  const result: ChildObj[] = []
  let firstTop: string | number = '' 
  hrefMap.forEach((value, key) => {
    const dom = document.getElementById(value)
    if (dom) {
      if (typeof firstTop !== 'number') {
        firstTop = dom.offsetTop
      }
      const obj:ChildObj = {
        href: key,
        el: dom,
        width: dom.offsetWidth,
        height: dom.offsetHeight,
        offsetTop: dom.offsetTop - firstTop + (props.offset || 0)
      }
      result.push(obj)
    }
  })

  return result.sort((a, b) => a.offsetTop - b.offsetTop)
}


const handleScroll = (e:Event) => {
  const target = e.target as HTMLElement
  const allChilds = computAllChild()
  for (let item of allChilds) {
    if (target.scrollTop + 1 < item.offsetTop + item.height) {
      curHref.value = item.href
      break
    }
  }
}

// 注册容器滚动事件
const registerScroll = () => {
  conatinerDom.value.addEventListener('scroll', handleScroll, false)
  // 设置初始锚点位置值
  const arr = computAllChild()
  curHref.value = arr[0].href
}

// 移除注册的滚动事件
const removeScroll = () => {
  conatinerDom.value.removeEventListener('scroll', handleScroll)
}
const setContainerDom = () => {
  if (!props.container) {
    conatinerDom.value = window
  } else if (typeof props.container === 'string') {
    const dom = document.getElementById(props.container)
    if (dom) {
      conatinerDom.value = dom
    }
  } else {
    conatinerDom.value = props.container
  }
}

/**
 * 注册锚点元素dom
 */
const registerEl = (href:string) => {
  hrefMap.set(href, href.replace('#', ''))
}

const removeEl = (href:string) => {
  hrefMap.delete(href)
}

/**
 * 设置当前锚点值
 */
const scrollTo = (href: string) => {
  const allChilds = computAllChild()
  for (let item of allChilds) {
    if (item.href === href) {
      conatinerDom.value.scrollTo({
        top: item.offsetTop,
        behavior: 'smooth'
      })
      // curHref.value = href
      break
    }
  }
}

watch(() => curHref.value, () => {
  let index = 0
  let rIndex = 0
  hrefMap.forEach((_value, key) => {
    if (curHref.value === key) {
      rIndex = index
    }
    index++
  })
  maskTop.value = 8 + 30 * rIndex + 'px'
})

/**注册子组件绑定的dom */
provide('curHref', curHref)
provide('registerEl', registerEl)
provide('removeEl', removeEl)
provide('scrollTo', scrollTo)

watch(() => props.container, () => {
  removeScroll()
  setContainerDom()
  registerScroll()
})
onMounted(() => {
  setContainerDom()
  registerScroll()
})

onUnmounted(() => {
  removeScroll()
})

defineExpose({
  scrollTo
})
</script>
<style lang="scss" scoped>
.bs-anchor {
  position: relative;
  .bs-anchor-masker {
    position: absolute;
    left: 0;
    width: 4px;
    height: 14px;
    background-color: var(--primary-color);
    border-radius: 2px;
    top: 8px;
    transition: all .2s;
  }
  &.underline {
    .bs-anchor-masker {
      width: 2px;
    }
    &::before {
      content: '';
      position: absolute;
      left: 0;
      width: 2px;
      height: 100%;
      background-color: #0505050f;
    }
  }
  .bs-anchor-list {
    padding-left: 14px;
  }
}
</style>
